package com.example.log_db.aspect;

import com.example.log_db.annotation.SpELLogRecord;
import com.example.log_db.config.LogConfig;
import com.example.log_db.model.LogInfo;
import com.example.log_db.service.LogService;
import com.example.log_db.util.SpELExpressionEvaluator;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;

/**
 * SpEL日志记录切面
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class SpELLogRecordAspect {

    private final LogService logService;
    private final LogConfig logConfig;
    private final ObjectMapper objectMapper;
    private final SpELExpressionEvaluator expressionEvaluator;

    /**
     * 定义切点 - 所有使用@SpELLogRecord注解的方法
     */
    @Pointcut("@annotation(com.example.log_db.annotation.SpELLogRecord)")
    public void logPointcut() {
    }

    /**
     * 环绕通知
     */
    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 如果日志记录功能未启用，直接执行原方法并返回结果
        if (!logConfig.isEnabled()) {
            return joinPoint.proceed();
        }

        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 获取注解信息
        SpELLogRecord logRecord = method.getAnnotation(SpELLogRecord.class);
        if (logRecord == null) {
            return joinPoint.proceed();
        }

        // 开始时间
        long startTime = System.currentTimeMillis();
        // 返回结果
        Object result = null;
        // 异常信息
        String exceptionMessage = null;
        // 日志信息构建器
        LogInfo.LogInfoBuilder logInfoBuilder = LogInfo.builder();

        // 如果需要在方法执行前解析表达式
        if (logRecord.parseBeforeExecution()) {
            // 在方法执行前解析表达式
            parseExpressions(joinPoint, method, logRecord, null, null, logInfoBuilder);
        }

        try {
            // 执行目标方法
            result = joinPoint.proceed();
            return result;
        } catch (Throwable e) {
            // 记录异常信息
            exceptionMessage = e.getMessage();
            throw e;
        } finally {
            // 结束时间
            long endTime = System.currentTimeMillis();
            
            // 如果不是在方法执行前解析表达式，则在方法执行后解析
            if (!logRecord.parseBeforeExecution()) {
                // 在方法执行后解析表达式
                parseExpressions(joinPoint, method, logRecord, result, exceptionMessage, logInfoBuilder);
            }
            
            // 异步记录日志
            recordLog(joinPoint, result, endTime - startTime, exceptionMessage, logInfoBuilder, logRecord);
        }
    }

    /**
     * 解析SpEL表达式
     */
    private void parseExpressions(ProceedingJoinPoint joinPoint, Method method, SpELLogRecord logRecord, 
                                 Object result, String exceptionMessage, LogInfo.LogInfoBuilder logInfoBuilder) {
        try {
            // 获取目标对象
            Object target = joinPoint.getTarget();
            // 获取方法参数
            Object[] args = joinPoint.getArgs();

            // 解析操作类型
            String operationType = logRecord.operationType();
            logInfoBuilder.operationType(operationType);

            // 解析操作描述
            String description = expressionEvaluator.parseExpression(
                    logRecord.description(), method, args, target, result, exceptionMessage);
            logInfoBuilder.description(description);

            // 解析操作人ID
            String operator = expressionEvaluator.parseExpression(
                    logRecord.operator(), method, args, target, result, exceptionMessage);
            if (!operator.isEmpty()) {
                logInfoBuilder.userId(operator);
            }

            // 解析业务ID
            String businessId = expressionEvaluator.parseExpression(
                    logRecord.businessId(), method, args, target, result, exceptionMessage);
            logInfoBuilder.businessId(businessId);

            // 解析业务类型
            String businessType = logRecord.businessType();
            logInfoBuilder.businessType(businessType);

            // 解析额外信息
            String extra = expressionEvaluator.parseExpression(
                    logRecord.extra(), method, args, target, result, exceptionMessage);
            logInfoBuilder.extra(extra);
        } catch (Exception e) {
            log.error("解析SpEL表达式异常", e);
        }
    }

    /**
     * 异步记录日志
     */
    @Async("logExecutor")
    public void recordLog(ProceedingJoinPoint joinPoint, Object result, long executionTime, 
                         String exceptionMessage, LogInfo.LogInfoBuilder logInfoBuilder, SpELLogRecord logRecord) {
        try {
            // 获取请求信息
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes != null ? attributes.getRequest() : null;

            // 获取方法签名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();

            // 设置方法信息
            logInfoBuilder.method(method.getDeclaringClass().getName() + "." + method.getName())
                    .executionTime(executionTime)
                    .operateTime(LocalDateTime.now());

            // 设置请求信息
            if (request != null) {
                logInfoBuilder
                        .requestUrl(request.getRequestURI())
                        .ip(getIpAddress(request));
                
                // 如果没有通过SpEL表达式设置用户ID，则尝试从请求中获取
                if (logInfoBuilder.build().getUserId() == null) {
                    logInfoBuilder.userId(getUserId(request));
                }
            }

            // 记录请求参数
            if (logRecord.recordParams()) {
                try {
                    String params = objectMapper.writeValueAsString(joinPoint.getArgs());
                    logInfoBuilder.requestParams(params);
                } catch (Exception e) {
                    logInfoBuilder.requestParams(Arrays.toString(joinPoint.getArgs()));
                }
            }

            // 记录返回结果
            if (logRecord.recordResult() && result != null) {
                try {
                    String resultJson = objectMapper.writeValueAsString(result);
                    logInfoBuilder.responseResult(resultJson);
                } catch (Exception e) {
                    logInfoBuilder.responseResult(result.toString());
                }
            }

            // 记录异常信息
            if (exceptionMessage != null) {
                logInfoBuilder.exceptionMessage(exceptionMessage);
            }

            // 保存日志
            logService.saveLog(logInfoBuilder.build());
        } catch (Exception e) {
            log.error("记录操作日志异常", e);
        }
    }

    /**
     * 获取请求IP地址
     */
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 获取用户ID
     * 实际项目中可以从请求头、Session或Token中获取
     */
    private String getUserId(HttpServletRequest request) {
        // 这里只是一个示例，实际项目中应该根据认证方式获取用户ID
        return "anonymous";
    }
}